package codex

import (
	"fmt"
	"github.com/pkg/errors"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"net/http"
	"strconv"
	"sync/atomic"
)

var (
	_messages atomic.Value           // NOTE: stored map[int]map[string]string
	_codes    = map[int64]struct{}{} // register codes.
)

func Register(cm map[int64]map[string]string) {
	_messages.Store(cm)
}

type Code int64

func New(e int64) Code {
	if e <= 0 {
		panic("business ecode must greater than zero")
	}
	return add(e)
}

func add(e int64) Code {
	if _, ok := _codes[e]; ok {
		panic(fmt.Sprintf("ecode: %d already exist", e))
	}
	_codes[e] = struct{}{}
	return Code(e)
}

type Codes interface {
	Error() string
	Code() int64
	Add(int64) Code
	Message(lang string) string
	Details() []interface{}
}

func (e Code) Add(i int64) Code {
	return Code(i)
}

func (e Code) Error() string {
	return strconv.Itoa(int(e))
}

func (e Code) Code() int64 { return int64(e) }

func (e Code) Int32() int32 {
	return int32(e)
}

func (e Code) Int() int {
	return int(e)
}

func (e Code) Message(lang string) string {
	if cm, ok := _messages.Load().(map[int64]map[string]string); ok {
		if msg, ok := cm[e.Code()][lang]; ok {
			return msg
		}
	}
	return e.Error()
}

// Details return details.
func (e Code) Details() []interface{} { return nil }

func Int(i int) Code     { return Code(i) }
func Int64(i int64) Code { return Code(i) }
func String(e string) Code {
	if e == "" {
		return OK
	}
	i, err := strconv.Atoi(e)
	if err != nil {
		return ServerErr
	}
	return Code(i)
}

func Cause(e error) Codes {
	if e == nil {
		return OK
	}
	ec, ok := errors.Cause(e).(Codes)
	if ok {
		return ec
	}
	return String(e.Error())
}

func Equal(a, b Codes) bool {
	if a == nil {
		a = OK
	}
	if b == nil {
		b = OK
	}
	return a.Code() == b.Code()
}

func EqualError(code Codes, err error) bool {
	return Cause(err).Code() == code.Code()
}

func ErrToCode(err error) int64 {
	if err == nil {
		return 200
	}
	fromError, ok := status.FromError(err)
	if ok {
		return int64(fromError.Code())
	}
	return Cause(err).Code()
}

func StatusFromGrpcStatus(err codes.Code) int {
	switch err {
	case codes.OK:
		return http.StatusOK
	case codes.Canceled:
		return http.StatusGatewayTimeout
	case codes.Unknown:
		return http.StatusInternalServerError
	case codes.InvalidArgument:
		return http.StatusBadRequest
	case codes.DeadlineExceeded:
		return http.StatusGatewayTimeout
	case codes.NotFound:
		return http.StatusNotFound
	case codes.AlreadyExists:
		return http.StatusConflict
	case codes.PermissionDenied:
		return http.StatusForbidden
	case codes.Unauthenticated:
		return http.StatusUnauthorized
	case codes.ResourceExhausted:
		return http.StatusTooManyRequests
	case codes.FailedPrecondition:
		return http.StatusBadRequest
	case codes.Aborted:
		return http.StatusConflict
	case codes.OutOfRange:
		return http.StatusBadRequest
	case codes.Unimplemented:
		return http.StatusNotImplemented
	case codes.Internal:
		return http.StatusInternalServerError
	case codes.Unavailable:
		return http.StatusServiceUnavailable
	case codes.DataLoss:
		return http.StatusInternalServerError
	}
	return http.StatusInternalServerError
}
